Ext.define('DutyList.calendar.view.AbstractCalendar', {
    extend: 'Ext.Component',
    alias: 'widget.calendarview',
    startDay: 0,
    enableFx: true,
    trackMouseOver: true,
    enableDD: true,
    monitorResize: true,
    ddCreateSheduleText: 'Create event for {0}',
    ddMoveSheduleText: 'Move event to {0} duty {1}',
    //////////***********
    sheduleSelector: '.ext-cal-evt',
    sheduleOverClass: 'ext-evt-over',
    sheduleElIdDelimiter: '-evt-',
    dayElIdDelimiter: '-day-',
    getSheduleBodyMarkup: Ext.emptyFn,
    getSheduleTemplate: Ext.emptyFn,

    // private
    initComponent: function() {
        this.setDate(this.Date || new Date());

        this.callParent(arguments);

        this.addEvents({

            eventsrendered: true,

            eventclick: true,

            eventover: true,

            eventout: true,

            datechange: true,

            rangeselect: true,

            eventmove: true,

            initdrag: true,

            dayover: true,

            dayout: true,

            eventdelete: true
        });
    },

    // private
    afterRender: function() {
        this.callParent(arguments);

        this.renderTemplate();

        if (this.store) {
            this.setStore(this.store, true);
        }
        
        this.on('resize', this.onResize, this);

        this.el.on({
            'mouseover': this.onMouseOver,
            'mouseout': this.onMouseOut,
            'click': this.onClick,
            scope: this
        });

        this.el.unselectable();
        if (this.enableDD && this.initDD) {
            this.initDD();
        }

        //todo////////////////////////////
        var qw=Ext.get('app-calendar-month').select('.ext-cal-evt').elements.length;
        ///////////////////////////////////

        this.on('eventsrendered', this.forceSize);
        Ext.defer(this.forceSize, 100, this);

    },
    initDD: function(){
        /*if(Ext.get('app-calendar-month-day-20120715-daytime')&&Ext.get('app-calendar-month-day-20120715-daytime').select('div')){
            var qw=Ext.get('app-calendar-month-day-20120715-daytime').select('div').elements.length;
            for (var i=2;i<qw;i+=2){
                console.log(Ext.get('app-calendar-month-day-20120715-daytime').select('div').elements[i]);
            }
        }*/
        this.on('eventsrendered', this.forceSize);
        Ext.defer(this.forceSize, 100, this);
    },

    forceSize: function() {
        var sizeScroll=17;
        if (this.el && this.el.down) {
            var hd = this.el.down('.ext-cal-hd-ct'),
                bd = this.el.down('.ext-cal-body-ct');
            if (bd==null || hd==null) {
                return;
            }
            var headerHeight = hd.getHeight(),
                sz = this.el.parent().getSize();
            hd.setWidth( bd.getWidth() - (this.getWeekCount()*25*this.maxCountShedulesPerDuty < this.el.down('.ext-cal-body-ct').getHeight()? 0 : sizeScroll));
            bd.setHeight(sz.height-headerHeight);
        }
    },

    refresh: function() {
        this.forceSize();
        this.prepareData();
        this.renderTemplate();
        this.renderItems();

    },

    getWeekCount: function() {
        var days = DutyList.calendar.util.Date.diffDays(this.viewStart, this.viewEnd);
        return Math.ceil(days / this.dayCount);
    },

    //разбивает события на подсобытия, разпихивает их по дням, в массивы
    //подготовить данные
    prepareData: function() {
        var lastInMonth = Ext.Date.getLastDateOfMonth(this.Date),
        w = 0,
        row = 0,
        dt = Ext.Date.clone(this.viewStart),
        weeks = this.weekCount < 1 ? 6: this.weekCount;
        this.sheduleGrid = [[]];
        this.maxCountShedulesPerDuty = 0;
        //возврает массив записей которые попадают в область отображения
        var evtsInView = this.store.queryBy(function(rec) {
            return this.isSheduleVisible(rec.data);
        },
        this);

        for (; w < weeks; w++) {
            if (this.weekCount == -1 && dt > lastInMonth) {
                //current week is fully in next month so skip
                break;
            }
            this.sheduleGrid[w] = this.sheduleGrid[w] || [];
            for (d = 0; d < this.dayCount; d++) {
                if (evtsInView.getCount() > 0) {
                    var evts = evtsInView.filterBy(function(rec) {
                        var startDt = Ext.Date.clearTime(rec.data[DutyList.calendar.data.SheduleMappings.StartDate.name], true);
                        return dt.getTime() == startDt.getTime();
                    },
                    this);
                    //сортировка все записей в дне, надо будет потом перепилить
                    this.sortSheduleRecordsForDay(evts);
                    //подготовить события для грида
                    this.prepareSheduleGrid(evts, w, d);
                }
                dt = DutyList.calendar.util.Date.add(dt, {days: 1});
            }
        }
    },

    // private
    //эта штука нужная для того чтобы растасовать события которые длятнся не больше дня
    prepareSheduleGrid: function(evts, w, d) {
        var row = 0,
        nightmax = 0,
        daymax = 0;
        evts.each(function(evt) {
            var M = DutyList.calendar.data.SheduleMappings,
                row = this.findEmptyRowIndex(w, d);
            this.sheduleGrid[w][d] = this.sheduleGrid[w][d] || [];
            this.sheduleGrid[w][d][row] = evt;
            if (evt.data[M.Night.name]){
                nightmax++;
            }else{
                daymax++;
            }
            this.maxCountShedulesPerDuty = Math.max(this.maxCountShedulesPerDuty,daymax,nightmax);
            return true;
        },
        this);
    },

    // private ищет в массиве событий пустую строчку
    findEmptyRowIndex: function(w, d) {
        var grid = this.sheduleGrid,
        day = grid[w] ? grid[w][d] || [] : [],
        i = 0,
        ln = day.length;

        for (; i < ln; i++) {
            if (day[i] == null) {
                return i;
            }
        }
        return ln;
    },

    // private
    renderTemplate: function() {
        if (this.tpl) {
            this.tpl.overwrite(this.el, this.getParams());
        }
    },

    // private
    onResize: function() {
        this.forceSize();
        this.renderTemplate();
    },

    // private
    onInitDrag: function() {
        this.fireEvent('initdrag', this);
    },

    //todo private
    onSheduleDrop: function(rec, dt) {
        if (DutyList.calendar.util.Date.compare(rec.data[DutyList.calendar.data.SheduleMappings.StartDate.name], dt) === 0) {
            // no changes
            return;
        }
        var diff = dt.getTime() - rec.data[DutyList.calendar.data.SheduleMappings.StartDate.name].getTime();
        rec.set(DutyList.calendar.data.SheduleMappings.StartDate.name, dt);

        this.fireEvent('eventmove', this, rec);
    },

    //private
    onCalendarEndDrag: function(dt,night, ddel) {
        if (dt && ddel) {
            // have to wait for the user to save or cancel before finalizing the dd interation
            var obj = {rec : this.getSheduleRecordFromEl(ddel),
                Night : night,
                StartDate : dt,
                updateObj : true
            }
            this.fireEvent('rangeselect', this, obj);
        }
    },

    //private
    onUpdate: function(ds, rec, operation) {
        if (operation == Ext.data.Record.COMMIT) {
            this.refresh();
        }
    },

    // private
    onAdd: function(ds, records, index) {
        var rec = records[0];
        this.tempSheduleId = rec.id;
        this.refresh();
    },

    // private
    onRemove: function(ds, rec) {
        this.getSheduleEls(rec.data[DutyList.calendar.data.SheduleMappings.SheduleId.name]).remove();
        this.refresh();
    },
    /**
     * Retrieve an Event object's id from its corresponding node in the DOM.
     * @param {String/Element/HTMLElement} el An {@link Ext.core.Element}, DOM node or id
     */
    getSheduleIdFromEl: function(el) {
        el = Ext.get(el);

        var id = el.id.split(this.sheduleElIdDelimiter)[1];
        lastHypen = (id !==undefined ? id.lastIndexOf('-') : -1);

        // MUST look for last hyphen because autogenned record IDs can contain hyphens
        if (lastHypen > -1) {
            //This id has the index of the week it is rendered in as the suffix.
            //This allows events that span across weeks to still have reproducibly-unique DOM ids.
            id = id.substr(0, lastHypen);
        }

        return id;
    },

    // private
    getSheduleId: function(sheduleId) {
        if (sheduleId === undefined && this.tempSheduleId) {
            sheduleId = this.tempSheduleId;
        }
        return sheduleId;
    },

    /**
     * 
     * @param {String} eventId
     * @param {Boolean} forSelect
     * @return {String} The selector class
     */
    getSheduleSelectorCls: function(sheduleId, forSelect) {
        var prefix = forSelect ? '.': '';
        return prefix + this.id + this.sheduleElIdDelimiter + this.getSheduleId(sheduleId);
    },

    /**
     * 
     * @param {String} eventId
     * @return {Ext.CompositeElement} The matching CompositeElement of nodes
     * that comprise the rendered event.  Any event that spans across a view 
     * boundary will contain more than one internal Element.
     */
    getSheduleEls: function(sheduleId) {
        var els = Ext.select(this.getSheduleSelectorCls(this.getSheduleId(sheduleId), true), false, this.el.dom);
        return new Ext.CompositeElement(els);
    },

    /**
     * Returns true if the view is currently displaying today's date, else false.
     * @return {Boolean} True or false
     */
    isToday: function() {
        var today = Ext.Date.clearTime(new Date()).getTime();
        return this.viewStart.getTime() <= today && this.viewEnd.getTime() >= today;
    },
    // private
    onDataChanged: function(store) {
        this.refresh();
    },

    isSheduleVisible: function(evt) {
        var start = this.viewStart.getTime(),
        end = this.viewEnd.getTime(),
        M = DutyList.calendar.data.SheduleMappings,
        data = evt.data || evt,
        evStart = data[M.StartDate.name].getTime();
        return (evStart >= start && evStart <= end);
    },

    getDayEl: function(dt) {
        return Ext.get(this.getDayId(dt));
    },

    getDayId: function(dt) {
        if (Ext.isDate(dt)) {
            dt = Ext.Date.format(dt, 'Ymd');
        }
        return this.id + this.dayElIdDelimiter + dt;
    },

    /**
     * Returns the start date of the view, as set by {@link #setStartDate}. Note that this may not 
     * be the first date displayed in the rendered calendar -- to get the start and end dates displayed
     * to the user use {@link #getViewBounds}.
     * @return {Date} The start date
     */
    getDate: function() {
        return this.Date;
    },

    /**
     * Sets the start date used to calculate the view boundaries to display. The displayed view will be the 
     * earliest and latest dates that match the view requirements and contain the date passed to this function.
     * @param {Date} dt The date used to calculate the new view boundaries
     */
    setDate: function(start, refresh) {
        this.Date = Ext.Date.clearTime(start);
        this.setViewBounds(start);
        this.store.load({
            params: {
                start: Ext.Date.format(this.viewStart, 'm-d-Y'),
                end: Ext.Date.format(this.viewEnd, 'm-d-Y')
            }
        });

        if (refresh === true) {
            this.refresh();
        }
        this.fireEvent('datechange', this, this.Date, this.viewStart, this.viewEnd);
    },

    // private
    setViewBounds: function(Date) {
        var start = Date || this.Date,
            offset = start.getDay() - this.startDay,
            Dt = DutyList.calendar.util.Date;

        switch (this.weekCount) {
        case - 1:
            // auto by month

            start = Ext.Date.getFirstDateOfMonth(start);
            offset = start.getDay() - this.startDay+(start.getDay()==0? 6: -1);

            this.viewStart = Dt.add(start, {days: -offset, clearTime: true});
            // start from current month start, not view start:
            var end = Dt.add(start, {months: 1, seconds: -1});
            // fill out to the end of the week:
            this.viewEnd = Dt.add(end, {days: 0});
            return;

        default:
            this.viewStart = Dt.add(start, {days: -offset, clearTime: true});
            this.viewEnd = Dt.add(this.viewStart, {days: this.weekCount * 7, seconds: -1});
        }
    },

    // private
    getViewBounds: function() {
        return {
            start: this.viewStart,
            end: this.viewEnd
        };
    },

    sortSheduleRecordsForDay: function(evts) {
        if (evts.length < 2) {
            return;
        }
        evts.sortBy(Ext.bind(function(evtA, evtB) {
            return evtA.Name > evtB.Name;
        }, this));
    },

    /**
     * Updates the view to contain the passed date
     * @param {Date} dt The date to display
     * @return {Date} The new view start date
     */
    moveTo: function(dt, noRefresh) {
        if (Ext.isDate(dt)) {
            this.setDate(dt);
            if (noRefresh !== false) {
                this.refresh();
            }
            return this.Date;
        }
        return dt;
    },

    /**
     * Updates the view to the next consecutive date(s)
     * @return {Date} The new view start date
     */
    moveNext: function(noRefresh) {
        return this.moveTo(DutyList.calendar.util.Date.add(this.viewEnd, {days: 1}));
    },

    /**
     * Updates the view to the previous consecutive date(s)
     * @return {Date} The new view start date
     */
    movePrev: function(noRefresh) {
        var days = DutyList.calendar.util.Date.diffDays(this.viewStart, this.viewEnd) + 1;
        return this.moveDays( - days, noRefresh);
    },

    /**
     * Shifts the view by the passed number of months relative to the currently set date
     * @param {Number} value The number of months (positive or negative) by which to shift the view
     * @return {Date} The new view start date
     */
    moveMonths: function(value, noRefresh) {
        return this.moveTo(DutyList.calendar.util.Date.add(this.Date, {months: value}), noRefresh);
    },

    /**
     * Shifts the view by the passed number of days relative to the currently set date
     * @param {Number} value The number of days (positive or negative) by which to shift the view
     * @return {Date} The new view start date
     */
    moveDays: function(value, noRefresh) {
        return this.moveTo(DutyList.calendar.util.Date.add(this.Date, {days: value}), noRefresh);
    },

    moveToday: function(noRefresh) {
        return this.moveTo(new Date(), noRefresh);
    },

     // инициализирует сторе для обработки сообщений
    setStore: function(store, initial) {
        if (!initial && this.store) {
            this.store.un("datachanged", this.onDataChanged, this);
            this.store.un("add", this.onAdd, this);
            this.store.un("remove", this.onRemove, this);
            this.store.un("update", this.onUpdate, this);
            this.store.un("clear", this.refresh, this);
        }
        if (store) {
            store.on("datachanged", this.onDataChanged, this);
            store.on("add", this.onAdd, this);
            store.on("remove", this.onRemove, this);
            store.on("update", this.onUpdate, this);
            store.on("clear", this.refresh, this);
        }
        this.store = store;
        if (store && store.getCount() > 0) {
            this.refresh();
        }
    },

    getSheduleRecord: function(id) {
        var idx = this.store.find(DutyList.calendar.data.SheduleMappings.SheduleId.name, id);
        return this.store.getAt(idx);
    },

    getSheduleRecordFromEl: function(el) {
        return this.getSheduleRecord(this.getSheduleIdFromEl(el));
    },

    //private
    getParams: function() {
        return {
            fullSize: (this.el && this.el.down && this.el.down('.ext-cal-body-ct') && (this.getWeekCount()*20*this.maxCountShedulesPerDuty*2 < this.el.down('.ext-cal-body-ct').getHeight())) ? true : false,
            maxCountShedulesPerDuty: this.maxCountShedulesPerDuty,
            viewStart: this.viewStart,
            viewEnd: this.viewEnd,
            Date: this.Date,
            dayCount: this.dayCount,
            weekCount: this.weekCount,
            title: this.getTitle()
        };
    },

    getTitle: function() {
        return Ext.Date.format(this.Date, 'F Y');
    },

    onClick: function(e, t) {
        var el = e.getTarget(this.sheduleSelector, 5);
        if (el) {
            var id = this.getSheduleIdFromEl(el);
            this.fireEvent('eventclick', this, this.getSheduleRecord(id), t);
            return true;
        }
    },

    // private
    onMouseOver: function(e, t) {
        if (this.trackMouseOver !== false && (this.dragZone == undefined || !this.dragZone.dragging)) {
            if (!this.handleSheduleMouseEvent(e, t, 'over')) {
                this.handleDayMouseEvent(e, t, 'over');
            }
        }
    },

    // private
    onMouseOut: function(e, t) {
        if (this.trackMouseOver !== false && (this.dragZone == undefined || !this.dragZone.dragging)) {
            if (!this.handleSheduleMouseEvent(e, t, 'out')) {
                this.handleDayMouseEvent(e, t, 'out');
            }
        }
    },

    // private todo разобратся
    handleSheduleMouseEvent: function(e, t, type) {
        var el = e.getTarget(this.sheduleSelector, 5, true),
            rel,
            els,
            evtId;
        if (el) {
            rel = Ext.get(e.getRelatedTarget());
            if (el == rel || el.contains(rel)) {
                return true;
            }

            evtId = this.getSheduleIdFromEl(el);

            if (this.sheduleOverClass != '') {
                els = this.getSheduleEls(evtId);
                els[type == 'over' ? 'addCls': 'removeCls'](this.sheduleOverClass);
            }
            this.fireEvent('event' + type, this, this.getSheduleRecord(evtId), el);
            return true;
        }
        return false;
    },

    // private
    getDateFromId: function(id, delim) {
        var parts = id.split(delim ? delim : this.dayElIdDelimiter);
        if (parts[parts.length - 1].indexOf('-nighttime')> -1){
            return {
               startDate : parts[parts.length - 1].split('-nighttime')[0],
               night : true
            }
        }
        if (parts[parts.length - 1].indexOf('-daytime')> -1){
            return {startDate : parts[parts.length - 1].split('-daytime')[0],
                    night : false
            }
        }
        return parts[parts.length - 1];
    },

    // private todo разобратся
    handleDayMouseEvent: function(e, t, type) {
        t = e.getTarget('td', 3);
        if (t) {
            if (t.id && t.id.indexOf(this.dayElIdDelimiter) > -1) {
                var dt = this.getDateFromId(t.id, this.dayElIdDelimiter),
                rel = Ext.get(e.getRelatedTarget()),
                relTD,
                relDate;

                if (rel) {
                    relTD = rel.is('td') ? rel: rel.up('td', 3);
                    relDate = relTD && relTD.id ? this.getDateFromId(relTD.id, this.dayElIdDelimiter) : '';
                }
                if (!rel || dt != relDate) {
                    var el = this.getDayEl(dt);
                    if (el && this.dayOverClass != '') {
                        el[type == 'over' ? 'addCls': 'removeCls'](this.dayOverClass);
                    }
                    this.fireEvent('day' + type, this, Ext.Date.parseDate(dt, "Ymd"), el);
                }
            }
        }
    },

    // TODO private обязательно пересмотреть в month
    renderItems: function() {
        throw 'This method must be implemented by a subclass';
    },
    
    // private
    destroy: function(){
        this.callParent(arguments);
        
        if(this.el){
            this.el.un('contextmenu', this.onContextMenu, this);
        }
        Ext.destroy(
            this.editWin,
            this.dragZone,
            this.dropZone
        );
    }
});